using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using JigLibX.Physics;

namespace Microsoft.Xna.Framework
{
  using XNAColor = Microsoft.Xna.Framework.Color;
  using MainGame;
  using Services;
  using MainGame.GamePlay;

  static public class Utilities
  {
    static public float dt(this GameTime gametime)
    {
      return (float)gametime.ElapsedGameTime.TotalSeconds;
    }

    static public Vector3 random_vector(this Random rand)
    {
      Func<float> r = () => (float)(rand.NextDouble() * 2.0 - 1.0);
      return Vector3.Normalize(new Vector3(r(), r(), r()));
    }

    static public Ray GetRay(this Viewport viewport, Vector2 touch, Matrix view, Matrix projection)
    {
      var near_touch = viewport.Unproject(new Vector3(touch.X, touch.Y, 0.0f), projection, view, Matrix.Identity);
      var far_touch = viewport.Unproject(new Vector3(touch.X, touch.Y, 1.0f), projection, view, Matrix.Identity);
      var touch_dir = Vector3.Normalize(far_touch - near_touch);
      return new Microsoft.Xna.Framework.Ray(near_touch, touch_dir);
    }

    static public GraphicsDevice GraphicsDevice(this Model model)
    {
      return model.Meshes[0].Effects[0].GraphicsDevice;
    }

    static public void DrawAsBody(this Model model, Body body, Matrix view, Matrix projection)
    {
      Vector3 p = body.Transform.Position;
      Matrix h = body.Transform.Orientation;
      var s = 1.0f;

      var world = model.World(p, h, s);

      model.Draw(world, view, projection);
    }

    static public void Draw(this Model model, Vector3 p, Matrix h, float s, Matrix view, Matrix projection)
    {
      var world = model.World(p, h, s);

      model.Draw(world, view, projection);
    }

    static public void Draw(this Model model, Vector3 p, Matrix h, float s, Color color, Matrix view, Matrix projection)
    {
      foreach (var mesh in model.Meshes)
      {
        foreach (BasicEffect fx in mesh.Effects)
          fx.DiffuseColor = color.ToVector3();
      }
      model.Draw(p, h, s, view, projection);
    }

    static public Matrix World(this Model model, Vector3 p, Matrix h, float s)
    {
      var world = Matrix.CreateScale(s) * (h) * Matrix.CreateTranslation(p);
      if (model.Tag == null)
      {
        var bs = model.Meshes.Select(m => m.BoundingSphere).Aggregate((a, c) => BoundingSphere.CreateMerged(a, c));
        world = Matrix.CreateTranslation(-bs.Center) * Matrix.CreateScale(1.0f / bs.Radius) * world;
      }
      return world;
    }

    static public void EnableShipLighting(this Model model)
    {
      foreach (var m in model.Meshes)
        foreach (BasicEffect fx in m.Effects)
        {
          fx.EnableDefaultLighting();
          fx.PreferPerPixelLighting = false;
        }
    }

    static public void MoveTowards(this Body self, Vector3 destination, float dt, float speed)
    {
      var towards_h = self.RotationTowards(destination);
      var heading = Quaternion.CreateFromRotationMatrix(self.Transform.Orientation);
      var heading1 = Quaternion.Slerp(heading, towards_h, dt);
      self.Orientation = Matrix.CreateFromQuaternion(heading1);
      self.Force = self.Orientation.Forward * self.Mass * speed;
      self.UpdateVelocity(dt);
    }

    static public Quaternion RotationTowards(this Body self, Vector3 destination)
    {
      var p1 = self.Position;
      var p2 = destination;
      var d1 = Vector3.Forward;
      var v12 = Vector3.Normalize(p2 - p1);
      var angle = (float)System.Math.Acos(Vector3.Dot(d1, v12));
      var axis = Vector3.Normalize(Vector3.Cross(d1, v12));
      return Quaternion.CreateFromAxisAngle(axis, angle);
    }

    static public void RotateAround(this Body self, Body other, float dt, float speed, float min_dist, float max_dist, IEnumerable<Body> obstacles)
    {
      var dist = Vector3.Distance(self.Position, other.Position);
      if (obstacles.Any())
      {
        var closest_obstacle = obstacles.First();
        var min_d = Vector3.Distance(self.Position, closest_obstacle.Position);
        if (min_d < dist)
        {
          foreach (var o in obstacles)
          {
            var d = Vector3.Distance(self.Position, o.Position);
            if (d < min_d)
            {
              min_d = d;
              closest_obstacle = o;
            }
          }
          self.RotateAround(closest_obstacle, dt, speed, min_dist, max_dist, Enumerable.Empty<Body>());
          return;
        }
      }

      var move_towards = MathHelper.SmoothStep(0.0f, 1.0f, (dist - min_dist) / (max_dist - min_dist));
      var move_away = 1.0f - move_towards;

      var towards_h = self.RotationTowards(other.Position);
      var away_h = other.RotationTowards(self.Position);
      var heading = Quaternion.CreateFromRotationMatrix(self.Transform.Orientation);
      var heading1 = Quaternion.Slerp(heading, towards_h, move_towards * dt);
      var heading2 = Quaternion.Slerp(heading1, away_h, move_away * dt);
      self.Orientation = Matrix.CreateFromQuaternion(heading2);
      self.Force = self.Orientation.Forward * self.Mass * speed;
      self.UpdateVelocity(dt);
    }

    static public Point ToPoint(this Vector2 v, Matrix screen_transform)
    {
      return new Point((int)(v.X / screen_transform.M11), (int)(v.Y / screen_transform.M22));
    }

    static public XNAColor ToXNAColor(this MainGame.GamePlay.CardColor color)
    {
      switch (color)
      {
        case MainGame.GamePlay.CardColor.White:
          return XNAColor.White;
        case MainGame.GamePlay.CardColor.Red:
          return XNAColor.Red;
        case MainGame.GamePlay.CardColor.Green:
          return XNAColor.Green;
        case MainGame.GamePlay.CardColor.Blue:
          return XNAColor.Blue;
      }
      return XNAColor.Gray;
    }

    static public GameContext GetContext(this Game game)
    {
      return (game.Services.GetService(typeof(GameContext)) as GameContext);
    }

    static public IInputManager GetInputManager(this Game game)
    {
      return (game.Services.GetService(typeof(IInputManager)) as IInputManager);
    }

    static public IBoardRenderer GetBoardRenderer(this Game game)
    {
      return (game.Services.GetService(typeof(IBoardRenderer)) as IBoardRenderer);
    }

    static public IBoardState GetBoardManager(this Game game)
    {
      return (game.Services.GetService(typeof(IBoardState)) as IBoardState);
    }

    static public IUIRenderer GetUIRenderer(this Game game)
    {
      return (game.Services.GetService(typeof(IUIRenderer)) as IUIRenderer);
    }

    static public T MaxBy<T>(this IEnumerable<T> seq, Func<T, int> f)
    {
      var max_by = seq.First();
      var max = f(max_by);
      foreach (var x in seq.Skip(1))
      {
        var fx = f(x);
        if (max < fx)
        {
          max = fx;
          max_by = x;
        }
      }
      return max_by;
    }

    static public void Begin(this SpriteBatch spriteBatch, Matrix transform)
    {
      spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, transform);
    }

  }
}